﻿
package extremefx.remoting {
	import extremefx.extensions.isNullOrEmpty;
	import extremefx.IDisposable;
	import extremefx.browser.Uri;
	import extremefx.events.EventHandler;
	import extremefx.events.ObjectEventArgs;
	import extremefx.events.StatusEventArgs;
	import extremefx.tools.Inflector;
	import extremefx.tools.StringUtils;

	import flash.events.Event;
	import flash.events.HTTPStatusEvent;
	import flash.events.IOErrorEvent;
	import flash.events.SecurityErrorEvent;
	import flash.net.URLLoader;
	import flash.net.URLLoaderDataFormat;
	import flash.net.URLRequest;
	import flash.net.URLRequestHeader;
	import flash.net.URLRequestMethod;
	import flash.utils.Dictionary;

	/**
	 * @author Marcelo Volmaro
	 */
	public class RestRequest implements IDisposable {
		private var _endPoint:String;
		private var _single:String;
		private var _list:String;
		private var _headers:Dictionary;
		private var _connections:Dictionary;
		
		protected var _dataFormat:String;
		private var _disposing:Boolean;
		private var _auth:IAuthorization;

		public function RestRequest(pEndPoint:String, pSingle:String, pList:String) {
			if (StringUtils.endsWith(pEndPoint, "/")) pEndPoint = StringUtils.trimEnd(pEndPoint, Vector.<String>(["/"]));
			if (isNullOrEmpty(pList)) pList = Inflector.pluralize(pSingle);
			
			_single = pSingle;
			_list = pList;
			_endPoint = pEndPoint;
			
			_dataFormat = URLLoaderDataFormat.TEXT;
			_headers = new Dictionary();
			_connections = new Dictionary();
		}
		
		public function read(pId:String):void {
			_request(pId, _single, null, null, "GET");
		}
		
		public function list(pQuery:Object = null):void {
			_request("", _list, null, pQuery, "GET");
		}
		
		protected function _update(pId:String, pData:*):void {
			_request(pId, _single, pData, null, "PUT");
		}
		
		protected function _insert(pData:*):void {
			_request("", _list, pData, null, "POST");
		}
		
		public function remove(pId:String):void {
			_request(pId, _single, null, null, "DELETE");
		}
		
		public function removeAll(pQuery:Object = null):void {
			_request("", _list, null, pQuery, "DELETE");
		}
		
		public function addHeader(pName:String, pValue:String):void {
			_headers[pName] = pValue;
		}
		
		public function removeHeader(pName:String):void {
			if (_headers[pName]) delete _headers[pName];
		}
		
		public function setAuthorization(pClass:IAuthorization):void {
			_auth = pClass;
		}
		
		private function _request(
			pId:String,
			pLocator:String,
			pData:* = null,
			pQuery:Object = null, 
			pRequestMethod:String = null):void {
				
			var uri:Uri = new Uri(_endPoint + "/" + pLocator + (pId ? "/" + pId : ""));
			uri.appendQueryData(pQuery);
			
			var req:URLRequest = uri.getUrlRequest();
			
			if (_auth is IAuthorization){
				_auth.authorize(this);
			}
		
			var headers:Array = [];
			for (var h:String in _headers){
				headers.push(new URLRequestHeader(h, _headers[h]));
			}
			
			if (!isNullOrEmpty(pRequestMethod)){
				headers.push(new URLRequestHeader("X-HTTP-Method-Override", pRequestMethod));
			}

			req.method = URLRequestMethod.POST;//if I try to use GET, flash does not sends the request headers........
			req.requestHeaders = headers;
			req.data = pData || "";//always set something here... again, if this is null, flash uses GET.
			
			var l:URLLoader = new URLLoader();
			l.dataFormat = _dataFormat;
			
			_addListener(l);
			_connections[l] = l;

			l.load(req);
		}
		
		private function _addListener(pLoader:URLLoader):void {
            pLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, _securityErrorHandler, false, 0, true);
            pLoader.addEventListener(HTTPStatusEvent.HTTP_STATUS, _httpStatusHandler, false, 0, true);
            pLoader.addEventListener(IOErrorEvent.IO_ERROR, _ioErrorHandler, false, 0, true);
			
			pLoader.addEventListener(Event.COMPLETE, _onComplete, false, 0, true);
		}

		private function _removeListener(pLoader:URLLoader):void {
			pLoader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, _securityErrorHandler);
            pLoader.removeEventListener(HTTPStatusEvent.HTTP_STATUS, _httpStatusHandler);
            pLoader.removeEventListener(IOErrorEvent.IO_ERROR, _ioErrorHandler);
			pLoader.removeEventListener(Event.COMPLETE, _onComplete);
		}
				
		private function _ioErrorHandler(event:IOErrorEvent):void {
			if (_eOnError) _eOnError.fire(new StatusEventArgs("", event.text));
		}

		private function _httpStatusHandler(event:HTTPStatusEvent):void {
			if (event.status == 200) return;
			
			if (_eOnError) _eOnError.fire(new StatusEventArgs(event.status.toString(), "HttpStatus"));
		}

		private function _securityErrorHandler(event:SecurityErrorEvent):void {
			if (_eOnError) _eOnError.fire(new StatusEventArgs("", "SecurityError"));
		}

		private function _onComplete(event:Event):void {
			var data:* = _process(URLLoader(event.target).data);
			var error:RemotingError = data as RemotingError; 
			
			if (error == null){
				if (_eOnComplete) _eOnComplete.fire(new ObjectEventArgs(data));
				
			} else {
				if (_eOnError) _eOnError.fire(new StatusEventArgs(error.ErrorCode.toString(), error.ErrorMessage));
			}
		}
		
		protected function _process(pData:*):* {
			return null;
		}
		
		public function dispose():void {
			if (_disposing) return;
			_disposing = true;
			
			for each(var i:URLLoader in _connections){
				_removeListener(i);
			}
			
			if (_eOnComplete) _eOnComplete.dispose();
			if (_eOnError) _eOnError.dispose();
			_connections = _headers = null;
		}
		
		private var _eOnComplete:EventHandler;
		public function get onComplete():EventHandler{
			if (_eOnComplete == null) _eOnComplete = new EventHandler(this);
			return _eOnComplete;
		}
		
		private var _eOnError:EventHandler;
		public function get onError():EventHandler{
			if (_eOnError == null) _eOnError = new EventHandler(this);
			return _eOnError;
		}
	}
}

import flash.net.registerClassAlias;

final class RemotingError {
	registerClassAlias("extremefx.remoting.RemotingError", RemotingError);
	
	public var ErrorCode:int;
	public var ErrorMessage:String;

	public function RemotingError(pCode:int = 400, pMessage:String = "") {
		ErrorCode = pCode;
		ErrorMessage = pMessage;
	}
}
